/*
 * @Author: your name
 * @Date: 2021-04-10 12:07:01
 * @LastEditTime: 2021-04-12 14:23:02
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \TypeScript\19-高级类型.ts
 */

// 
// 字符串字面量类型 
// 
// 字符串字面量类型 允许你指定字符串必须的固定值
// 在实际应用中，字符串字面量可以与连个类型，类型保护和类型别名很好的配合。
// 通过结合使用这些特性，你可以事先类似枚举类型的字符串
type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
    animate(dx: number, dy: number, easing: Easing) {
        if (easing === "ease-in") {
            // ...
        } else if (easing === "ease-in-out") {

        } else if (easing === "ease-out") {

        } else {
            // error!shoule not pass null or undefined.
        }
    }
}
let button = new UIElement();
button.animate(0, 0, "ease-in");
// button.animate(0, 0, "uneasy");//error:"uneasy" is not allowed here
// 字符串字面量类型还可以用于区分函数重载
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLImageElement;
// ...more overloads ...
function createElement(tagName: string): Element {
    // ...code goew here ...
    return;
}

// 数字字面量类型
// typescript 还具有 数字字面量类型
function rollDie(): 1 | 2 | 3 | 4 | 5 | 6 {
    return;
}

// 可辨识联合(Discriminated Unions)
// 你可以合并单例类型，联合类型，类型保护和类型别名来创建一个叫做 可辨识联合地好几模式
// 它也称作标签联合或 代数数据类型。可辨识连个在函数式编程很有用处。
// 一些语言会自动地为你辨识连个，而typescript则基于已有的JavaScript模式，它有三个要素:
// a).具有普通的单例类型属性-可辨识的特征
// b).一个类型别名包含了那些类型的联合-联合
// c).此属性上的类型保护
// 首先我们声明了将要联合的接口。每个接口都有kind属性但有不同的字符串字面量类型，
interface Square {
    kind: "Square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    height: number;
    width: number;
}
interface Circle {
    kind: "Circle";
    radius: number;
}
// kind属性称作 可辨识的特征或标签，其他的属性则特定于各个接口。
// 注意，目前各个接口间是没有联系的。下面我们把他们联合到一起
type Shap = Square | Circle | Rectangle;
function area(s: Shap) {
    switch (s.kind) {
        case "Square": return s.size * s.size;
        case "Circle": return Math.PI * s.radius ** 2;
        case "rectangle": return s.width * s.height;
    }
}
// 完整性检查
// 当没有涵盖所有可辨识联合的变化时，我们想让编译器可以通知我们
// 比如，如果我们添加了Triangle到Shape，我们同时还需要更新area:
interface Triangle {
    kind: 'Triangle';
    val: undefined;
}
type Shap2 = Square | Circle | Rectangle | Triangle;
function area2(s: Shap2) {
    switch (s.kind) {
        case "Square": return s.size * s.size;
        case "Circle": return Math.PI * s.radius ** 2;
        case "rectangle": return s.width * s.height;
        // 此处需要跟上新增的Triangle，不然不起作用，单数typescript是不会报错的
        // 因为此处要是传入的s是typescript类型的，那么函数area会返回一个undefined是any类型的子类型，所有默认不报错
        default: return s.kind + "-" + s.val;
    }
}
// 有两种方式可以实现。
// 1.首先是启用 --strictNullChecks 并且指定一个返回值类型：
interface Triangle {
    kind: 'Triangle';
    val: undefined;
}
type Shap3 = Square | Circle | Rectangle | Triangle;
function area3(s: Shap3): number {
    switch (s.kind) {
        case "Square": return s.size * s.size;
        case "Circle": return Math.PI * s.radius ** 2;
        case "rectangle": return s.width * s.height;
        // 此处需要跟上新增的Triangle，不然不起作用，单数typescript是不会报错的
        // 因为此处要是传入的s是typescript类型的，那么函数area会返回一个undefined是any类型的子类型，所有默认不报错
        // default: return s.kind + "-" + s.val
    }
}
// 当我们使用了严格空值检查后，只有明确知道了undefined类型的值才允许被设置为控制，否则不允许
// 此处我们给函数定义了一个数字类型的返回值，一旦我们给函数传入了一个Triangle类型的参数，
// 此时因为case里无法匹配，返回的必然就是undefined这个非法值，所以编译器就报错了
// 2.第二种方法使用never类型，编译器用它来进行完整性检查:
type Shap4 = Square | Circle | Rectangle | Triangle;
function assertNever(x: never): never {
    throw new Error("Unexpected object:" + x);
}
function area4(s: Shap4): number {
    switch (s.kind) {
        case "Square": return s.size * s.size;
        case "Circle": return Math.PI * s.radius ** 2;
        case "rectangle": return s.width * s.height;
        // default: return assertNever(null);// error here if there are missing cases
    }
}
// assertNever检查 s是否为never类型-即为去除所有可能情况后剩下的类型。如果你忘记某个case，
// 那么s将具有一个真实的类型并且你会得到一个错误。这种方式需要你定义一个额外的函数，
// 但是在你忘记某个case的时候也更加明显

// 多态的this类型检查
// 多态的 this类型表示的是某个包含类或接口的子类型。这被称作F-bounded多态性。
// 它能很容易的表现连贯接口间的继承(即所谓的链式操作)
// 比如。在计算器的例子里，在每个操作之后都返回this类型或是jquery函数返回的类型
class BasicCalculator {
    public constructor(protected value: number = 0) { }
    public currentValue(): number {
        return this.value;
    }
    public add(operand: number): this {
        this.value *= operand;
        return this;
    }
    public multiply(operand: number): this {
        this.value *= operand;
        return this;
    }
    // ...other operations go here...
}
let v = new BasicCalculator(2)
    .multiply(5)
    .add(1)
    .currentValue();


// 多态的this类型检查
// 由于前页的BasicCalculator这个类使用了this类型，你可以继承它，
// 新的类可以直接使用之前额方法，不需要做任何的改变
class ScientificCalculator extends BasicCalculator {
    public constructor(value = 0) {
        super(value);
    }
    public sin() {
        this.value = Math.sin(this.value);
        return this;
    }
    // ...other operations go here...
}
let v2 = new ScientificCalculator(2)
    .multiply(5)
    .sin()
    .add(1)
    .currentValue();
// 如果没有this类型，
// ScientificCalculator就不能够在继承 BasicCalculator的同时还保持接口的连贯性


// 索引类型
// 使用索引类型，编译器就能够检查使用了动态属性名的代码
// 例如：一个产概念的JavaScript模式就是从对象中选取属性的子集
function pluck(o, names) {
    return names.map(n => o[n]);
}
// 下面是如何在typescript里使用此函数，通过搜索引类型查询和索引访问操作符:
function pluck2<T, K extends keyof T>(o: T, names: K[]): T[K][] {
    return names.map(n => o[n]);
}
interface Person {
    name: string;
    age: number;
}
let person: Person = {
    name: 'jarid',
    age: 35
};
let strings: string[] = pluck(person, ['name']);//ok,string[] 
// 编译器会见擦name是否着呢是Person的一个属性，上页示例还引入了几个新的类型操作符。
// 首先是keyof T，索引类型查询操作符，对于任何类型 T，keyof T的结果为 T 上已知的公共属性名的联合
let personProps: keyof Person;//"name"|"age"
// keyof Person是完全可以与 "name" | "age" 互相替换的，不同的是如果你添加了其他的属性到Person，
// 例如addrss：string，那么keyof Person会自动变为 "name" | "age" | "address"
// 你可以在向pluck函数这类上下文里使用 keyof 因为在使用之前你并不清楚可能出现的属性名。但编译器会检查你是否出了正确的属性名给pluck;


// 索引类型(Index types)之索引访问操作符
// 操作符是T[k]，索引访问操作符
// 在这里，类型语法反映了表达式语法。这意味着person['name']具有类型Person['name'],在我们的例子里则为string类型
// 然而，就像索引类型查询一样，你可以在普通的上下文里使用T[k],这正是它的强大所在，你只要确保类型变量K extends keyof T就可以了
// 例如下面 getProperty函数的例子：
function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
    return o[name]; // o.name is of type T[K]
}
// getProperty里的o:T和name:K,意味着o[name]:T[K]。当你返回T[K]的结果，编译器会实例化键的真实类型，因此getProperty的返回值类型会随着你需要的属性改变
let name1: string = getProperty(person, 'name');
let age: number = getProperty(person, 'age');
// let unknow=getProperty(person,'unknow');//error,'unknow' is not in 'name' | 'age'

// 索引类型之字符串索引签名
// keyof 和 T[k] 与字符串索引签名进行交互
// 如果你有一个带有字符串索引签名的类型，那么keyof T 回事string 。并且T[string]为索引签名类型：
interface Map<T> { [key: string]: T; }
let keys: keyof Map<number>;//string
let value: Map<number>['foo'];//number


// 映射类型
// 一个常见的任务就是将一个一直的类型的每个属性都变为可选的：
interface PersonPartial {
    name?: string;
    age?: number;
}
// 或者我们想要一个只读版本
interface personReadonly {
    readonly name: string;
    readonly age: number;
}
// TypeScript提供了从旧类型中创建新类型的一种方式 - 映射类型。在映射类型里，新类型以相同的形式去转换类型里的每个属性
// 例如：你可以令每个属性成为readonly类型或可选的
type Readonly_<T> = {
    readonly [P in keyof T]: T[P];
}
type Partial_<T> = {
    [P in keyof T]?: T[P]
}
type PersonPartial2 = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;

/**
 * 2021 4 13
 * ldq
 * link
 */
// 注意Readonly<T>和Partial<T>用处不小，因此它们与Pick和Record一同被包含进了TypeScript地标准库里：
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
}
type Record<K extends string, T> = {

}
// Readonly,Partial和Pick 是同态的，但Record不是。因为Record并不需要输入类型来拷贝属性，所以它不属于同态：
type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>
// 非同态类型本质上会创建新的属性，因此它们不会从它处拷贝属性修饰符
// 在真正的应用里，可能不同于上面的Readonly或Partial。他们会给予一些已存在的类型，
// 且按照一定的方式转换字段，这就是keyof和索引访问类型要做的事情：
interface Person {
    name: string;
    age: number;
}
let person2: Person = {
    name: 'jarid',
    age: 35
}
// 新的类型是包含了person里的每一个规则的同时还可以设置null
type NullablePerson = { [P in keyof Person]: Person[P] | null }
// 新的类型是包含了person里的每一个规则的同时，每一个都是可选属性
type PartialPerson = { [P in keyof Person]?: Person[P] }
// 通用版本
type Nullable_<T> = { [P in keyof T]: T[P] | null }
type Partial2_<T> = { [P in keyof T]?: T[P] }



// 由映射类型进行推断
// 预定于的有条件类型
// typescript2.8在lib.d.ts里增加了一些预定义的有条件类型：
// Exclude <T,U> -- 从T中提出可以赋值给U的类型
// Extract <T,U> -- 提取T中可以赋值给U的类型
// NonNullablr <T> -- 从T中提出可以赋值给U的类型
// ReturnTypes <T,U> -- 获取函数返回值类型instanceType<T> -- 获取构造函数类型的实例类型
type T00 = Exclude<"a" | "b" | "c", "a" | "c" | "f">;
type T01 = Exclude<"a" | "b" | "c", "a" | "c" | "f">;

type T02 = Exclude<string | number | (() => void), Function>;
type T03 = Exclude<string | number | (() => void), Function>;

type T04 = NonNullable<string | number | undefined>;
type T05 = NonNullable<(() => string) | string[] | null | undefined>;

function f1(s: string) {
    return { a: 1, b: s };
}